﻿using System.Text;
using System;
using System.Collections;

// http://en.wikipedia.org/wiki/Modbus
// http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf
// http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf
// http://www.simplymodbus.ca/FC01.htm
// http://en.radzio.dxp.pl/modbus-master-simulator/

namespace sinetd.Services.Modbus
{
    public class Service : BaseService, IService
    {
        public Service()
            : base("Modbus")
        {
        }

        public delegate void FrameHandler(ref Frame frame);
        public event FrameHandler NewFrameHandler;
        private void OnFrameHandler(ref Frame frame)
        {
            if (NewFrameHandler != null)
                NewFrameHandler(ref frame);
        }

        public DataModel DataModel;

        #region IService Members

        public void HandleNewConnection(IConnectionIO conn)
        {
            Encoding enc = Encoding.GetEncoding(1252);
            byte[] bytesIn = enc.GetBytes(conn.Recv());

            Frame frame = new Frame();
            frame.Decode(bytesIn);

            OnFrameHandler(ref frame);

            byte[] bytesOut = frame.Encode();
            conn.Send(enc.GetString(bytesOut));
        }

        public void Shutdown()
        {
        }

        #endregion

        public Frame Answer(Frame frame)
        {
            Frame output = new Frame();
            output.Device = frame.Device;
            output.FunctionCode = frame.FunctionCode;

            int count;
            IDataTableReload table;
            BitArray ba;
            switch (frame.FunctionCode)
            {
                case FunctionCodes.ReadCoilStatus:
                    count = (frame.DataInB / 8);
                    if ((frame.DataInB % 8) != 0) count++;
                    output.Data = new byte[count + 1];
                    output.Data[0] = (byte)count;

                    table = (IDataTableReload)DataModel.Tables[(int)MemoryBanks.DiscreteInput];
                    ba = new BitArray(count * 8);
                    for (int i = frame.DataInA; i < (frame.DataInA + frame.DataInB); i++)
                    {
                        ba.Set(i, (bool)table.GetRawData(i));
                    }

                    ba.CopyTo(output.Data, 1);
                    break;

                case FunctionCodes.ForceSingleCoil:
                    table = (IDataTableReload)DataModel.Tables[(int)MemoryBanks.Coils];
                    table.SetRawData(frame.DataInA, frame.DataInB == 1);
                    break;

                case FunctionCodes.ReadInputStatus:
                    count = (frame.DataInB / 8);
                    if ((frame.DataInB % 8) != 0) count++;
                    output.Data = new byte[count + 1];
                    output.Data[0] = (byte)count;

                    table = (IDataTableReload)DataModel.Tables[(int)MemoryBanks.Coils];
                    ba = new BitArray(count * 8);
                    for (int i = frame.DataInA; i < (frame.DataInA + frame.DataInB); i++)
                    {
                        ba.Set(i, (bool)table.GetRawData(i));
                    }

                    ba.CopyTo(output.Data, 1);
                    break;
                case FunctionCodes.ReadHoldingRegisters:
                    count = (frame.DataInB / 8);
                    if ((frame.DataInB % 8) != 0) count++;
                    output.Data = new byte[count + 1];
                    output.Data[0] = (byte)count;

                    table = (IDataTableReload)DataModel.Tables[(int)MemoryBanks.HoldingRegisters];
                    ba = new BitArray(count * 8);
                    for (int i = frame.DataInA; i < (frame.DataInA + frame.DataInB); i++)
                    {
                        output.Data[i] = (byte)(((short)table.GetRawData(i)) << 8);
                        output.Data[i] = (byte)(((short)table.GetRawData(i)));
                    }

                    ba.CopyTo(output.Data, 1);
                    break;
                case FunctionCodes.ReadInputRegisters:
                    count = (frame.DataInB / 8);
                    if ((frame.DataInB % 8) != 0) count++;
                    output.Data = new byte[count + 1];
                    output.Data[0] = (byte)count;

                    table = (IDataTableReload)DataModel.Tables[(int)MemoryBanks.InputRegisters];
                    ba = new BitArray(count * 8);
                    for (int i = frame.DataInA; i < (frame.DataInA + frame.DataInB); i++)
                    {
                    }

                    ba.CopyTo(output.Data, 1);
                    break;
            }
            return output;
        }
    }
}
